﻿using UnityEngine;
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;

namespace Baiyy {
	namespace Resource {
		public class ResourceUpdator {
			#region types
			public enum UpdateStage {
				LoadingRemoteVersion = 1,		// 加载远程版本文件
				CheckingUpdates,				// 检查更新
				Updating,						// 更新中
				UpdateFinished					// 更新完毕
			}

			public class UpdateItem {
				public enum UpdateType {
					Update,
					Delete
				}

				public string name;
				public string md5;
				public UpdateType type;
				public int retryCount;

				public UpdateItem(string name, string md5, UpdateType type) {
					this.name = name; this.md5 = md5; this.type = type;
					retryCount = 0;
				}
			}

			public delegate void OnSetRemoteVersion();
			public delegate void OnStageSwitch(UpdateStage stage);
			public delegate void OnBeginUpdateItem(UpdateItem item);
			public delegate void OnFailedUpdateItem(UpdateItem item, string error);
			public delegate void OnFinishedUpdateItem(UpdateItem item, int updatedItems);
			#endregion

			#region vars
			string _remoteURL;
			HashtableEx _localVersion;
			HashtableEx _remoteVersion;
			MonoBehaviour _mono;
			bool _updateOk;
			List<UpdateItem> _updateItems;
			int _totalItems;
			int _updatedItems;

			OnSetRemoteVersion _onSetRemoteVersion;
			OnStageSwitch _onStageSwitch;
			OnBeginUpdateItem _onBeginUpdateItem;
			OnFailedUpdateItem _onFailedUpdateItem;
			OnFinishedUpdateItem _onFinishedUpdateItem;

			internal static int sRetryCount = 3;
			internal static ResourceUpdator _inst;
			#endregion

			#region getter&setter
			public static ResourceUpdator inst {
				get {
					if (_inst == null) {
						_inst = new ResourceUpdator ();
					}
					return _inst;
				}
			}
			public static int retryCount {
				get { return sRetryCount; }
				set { sRetryCount = value; }
			}
			public string localVersion {
				get { 
					if (_localVersion != null) {
						return _localVersion.asInt ("version") + "";
					}
					return null;
				}
			}
			public string remoteVersion {
				get { 
					if (_remoteVersion != null) {
						return _remoteVersion.asInt ("version") + "";
					}
					return null;
				}
			}

			public bool isUpdateOk{ get { return _updateOk; } }
			public List<UpdateItem> updateList {get { return _updateItems; }}
			public int totalItems { get { return _totalItems; } }

			public OnSetRemoteVersion onSetRemoteVersion{
				get { return _onSetRemoteVersion; }
				set { _onSetRemoteVersion = value; }
			}
			public OnStageSwitch onStageSwitch{
				get { return _onStageSwitch; }
				set { _onStageSwitch = value; }
			}
			public OnBeginUpdateItem onBeginUpdateItem{
				get { return _onBeginUpdateItem; }
				set { _onBeginUpdateItem = value; }
			}
			public OnFailedUpdateItem onFailedUpdateItem{
				get { return _onFailedUpdateItem; }
				set { _onFailedUpdateItem = value; }
			}
			public OnFinishedUpdateItem onFinishedUpdateItem{
				get { return _onFinishedUpdateItem; }
				set { _onFinishedUpdateItem = value; }
			}
			#endregion

			#region methods
			public ResourceUpdator() {
				_remoteURL = null;
				_localVersion = null;
				_remoteVersion = null;
				_mono = null;
				_updateOk = false;
				_updateItems = new List<UpdateItem> ();
				_totalItems = 0;
				_updatedItems = 0;

				_onSetRemoteVersion = null;
				_onStageSwitch = null;
				_onBeginUpdateItem = null;
				_onFailedUpdateItem = null;
				_onFinishedUpdateItem = null;
			}

			/// <summary>
			/// 初始化。
			/// </summary>
			/// <param name="mono">Mono.</param>
			public bool Init(MonoBehaviour mono) {
				_mono = mono;

				_localVersion = GetLocalVersion ();
				return _localVersion != null;
			}

			/// <summary>
			/// 启动更新。
			/// </summary>
			/// <param name="remoteURL">服务器地址</param>
			public void StartUpdate(string remoteURL) {
				_remoteURL = remoteURL;

				SwitchStage (UpdateStage.LoadingRemoteVersion);
				_mono.StartCoroutine (GetRemoteVersion (()=>{
					if (_onSetRemoteVersion != null) {
						_onSetRemoteVersion();
					}

					_updateOk = true;

					// 没有远程版本，更新完成
					if (_remoteVersion == null) {
						SwitchStage (UpdateStage.UpdateFinished);
						return;
					}

					// 比较版本号，检查是否需要更新
					int localVerNo = _localVersion.asInt("version");
					int remoteVerNo = _remoteVersion.asInt("version");
					if (localVerNo >= remoteVerNo) {
						SwitchStage (UpdateStage.UpdateFinished);
						return;
					}

					// 检查需要更新的资源
					SwitchStage (UpdateStage.CheckingUpdates);
					CheckUpdate();

					// 开始更新
					SwitchStage (UpdateStage.Updating);
					_mono.StartCoroutine(Update());
				}));
			}

			/// <summary>
			/// 切换阶段。
			/// </summary>
			/// <param name="stage">Stage.</param>
			void SwitchStage(UpdateStage stage) {
				if (_onStageSwitch != null) {
					_onStageSwitch (stage);
				}
			}

			/// <summary>
			/// 检查更新。
			/// </summary>
			void CheckUpdate() {
				// 本地资源表 
				Hashtable localRes = new Hashtable ();
				ArrayList localFiles = _localVersion.asArray ("files");
				foreach (object oFile in localFiles) {
					HashtableEx localFile = new HashtableEx (oFile as Hashtable);
					localRes [localFile.asString ("name")] = localFile.asString ("md5");
				}

				// 远程资源表 
				Hashtable remoteRes = new Hashtable ();
				ArrayList remoteFiles = _remoteVersion.asArray ("files");
				foreach (object oFile in remoteFiles) {
					HashtableEx remoteFile = new HashtableEx (oFile as Hashtable);
					remoteRes [remoteFile.asString ("name")] = remoteFile.asString ("md5");
				}

				// 计算需要删除的资源
				foreach(string name in localRes.Keys) {
					if (!remoteRes.ContainsKey (name)) {
						_updateItems.Add (new UpdateItem (name, null, UpdateItem.UpdateType.Delete));
					}
				}

				// 计算需要更新的资源
				foreach (string name in remoteRes.Keys) {
					string remoteMd5 = remoteRes [name] as string;
					if (localRes.ContainsKey (name) 
						&& (localRes [name] as string) == remoteMd5) {
						continue;
					}
					_updateItems.Add (new UpdateItem (name, remoteMd5, UpdateItem.UpdateType.Update));
				}

				_totalItems = _updateItems.Count;
			}

			/// <summary>
			/// 更新。
			/// </summary>
			IEnumerator Update() {
				while (_updateOk) {
					UpdateItem uItem = null;
					if (_updateItems.Count > 0) {
						uItem = _updateItems [0];
						_updateItems.RemoveAt (0);
					} else {
						break;
					}

					if (_onBeginUpdateItem != null) {
						_onBeginUpdateItem (uItem);
					}

					string fullName = Config.writablePath + uItem.name;

					if (uItem.type == UpdateItem.UpdateType.Delete) {
						// 删除多余的文件
						if (File.Exists(fullName)) {
							File.Delete (fullName);
						}
						_updatedItems += 1;
						if (_onFinishedUpdateItem != null) {
							_onFinishedUpdateItem (uItem, _updatedItems);
						}
						continue;
					}

					// 下载文件
					WWW www = new WWW (_remoteURL + uItem.name);
					yield return www;

					if (string.IsNullOrEmpty (www.error)) {
						// 下载成功，写入文件
						FileInfo fi = new FileInfo (fullName);
						Stream sw = null;
						if (!fi.Exists) {
							sw = fi.Create ();
						} else {
							sw = fi.Open (FileMode.Truncate);
						}
						sw.Write (www.bytes, 0, www.bytes.Length);
						sw.Flush ();
						sw.Close ();

						_updatedItems += 1;
						if (_onFinishedUpdateItem != null) {
							_onFinishedUpdateItem (uItem, _updatedItems);
						}
					} else {
						// 下载失败，重试
						if (_onFailedUpdateItem != null) {
							_onFailedUpdateItem (uItem, www.error);
						}

						if (uItem.retryCount >= sRetryCount) {
							_updateOk = false;
							continue;
						}

						uItem.retryCount += 1;
						_updateItems.Add (uItem);
					}
				}

				if (_updateOk) {
					// 更新版本文件 
					string verStr = MiniJSON.JsonEncode (_remoteVersion.data);
					StreamWriter sw = new StreamWriter (
						Config.writablePath + "/" + Config.versionFileName);
					sw.Write (verStr);
					sw.Flush ();
					sw.Close ();
				}

				SwitchStage (UpdateStage.UpdateFinished);
			}
				
			/// <summary>
			/// 获取本地版本文件。
			/// </summary>
			/// <returns>The local version.</returns>
			HashtableEx GetLocalVersion() {
				return new HashtableEx(MiniJSON.JsonDecode(File.ReadAllText(Config.localVersionFilePath)) as Hashtable);
			}

			/// <summary>
			/// 获取远程版本文件。
			/// </summary>
			/// <returns>The remote version.</returns>
			/// <param name="callback">Callback.</param>
			IEnumerator GetRemoteVersion(Action callback) {
				WWW www = new WWW (_remoteURL + "/" + Config.versionFileName);
				yield return www;

				if (string.IsNullOrEmpty (www.error)) {
					_remoteVersion = new HashtableEx(
						MiniJSON.JsonDecode(www.text) as Hashtable);
				}
				callback ();
			}
			#endregion
		}
	}
}
